package com.i72.basic.init;

import com.i72.basic.aop.AOPAdvice;
import com.i72.basic.aop.AOPAdvicePlus;
import com.i72.basic.context.GlobalContext;
import com.i72.basic.context.ThreadContext;
import com.i72.basic.dynamic.JavassistCode;
import com.i72.basic.tools.BeanHelper;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.config.AutowireCapableBeanFactory;
import org.springframework.beans.factory.support.AbstractBeanDefinition;
import org.springframework.beans.factory.support.GenericBeanDefinition;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.support.GenericApplicationContext;
import org.springframework.util.StringUtils;

import javax.annotation.Resource;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.util.*;
import java.util.regex.Pattern;

@Slf4j
public class AOPProxyInit implements ApplicationContextAware {

    private String advice1 = AOPAdvice.class.getName();
    private String advice2 = AOPAdvicePlus.class.getName();

    private GenericApplicationContext app = null;
    private Class aopAdvice;

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {

        this.app = (GenericApplicationContext)applicationContext;
        /*
        String [] beans = applicationContext.getBeanNamesForAnnotation(AOPAnnotation.class);


         */

        List<String> beans = new ArrayList<>();
        List<Object> aopCondition = ThreadContext.getList("aop");
        if(aopCondition!=null && aopCondition.size()>0){

            for(Object o:aopCondition){
                Map m = (Map)o;


                Object target =  ((List)m.get("target")).get(0);

                //Class c = (Class)list.get(0);

                Class c2 = (Class)((List)m.get("invoke")).get(0);

                if(c2!=null) {
                    Class [] interfaces = c2.getInterfaces();
                    if(interfaces!=null && interfaces.length>0){
                        if(interfaces[0].getName().equals(advice1) || interfaces[0].getName().equals(advice2)){
                            this.aopAdvice = c2;

                            if(!app.isBeanNameInUse(c2.getName())){
                                //没有注册，自动注册为bean
                                BeanHelper.registerBean(app.getBeanFactory(),"","",false,false,c2);
                            }

                        }else{
                            log.info("AOP工具没有实现特定接口，特定接口必须是"+advice1+",或者"+advice2);
                            return;
                        }
                    }else{
                        log.info("AOP工具没有实现特定接口，特定接口必须是"+advice1+",或者"+advice2);
                        return;
                    }
                }else{
                    log.info("AOP工具没有aop advice实现类.");
                    return;
                }

                Boolean around = (Boolean)((List)m.get("around")).get(0);

                //Class cc = null;




                if ((target instanceof Class) && ((Class)target).isAnnotation()) {

                    if(((Class)target).getName().contains(GlobalContext.getAPPPackage())) {
                        //只拦截自定义注解。即本包的注解，其他的不支持
                        beans.addAll(Arrays.asList(app.getBeanNamesForAnnotation(((Class)target))));

                    }
                }else if((target instanceof String) && !StringUtils.isEmpty((String)target)){

                    Pattern p = Pattern.compile((String) target,Pattern.CASE_INSENSITIVE);

                    String [] beanarr = app.getBeanDefinitionNames();
                    for(String bean:beanarr){
                        if(bean.contains(GlobalContext.getAPPPackage()) && p.matcher(bean).matches()){
                            //必须是本包的类，必须符合名字规则
                            beans.add(bean);
                        }
                    }
                } else {
                    if (c2.getName().contains(GlobalContext.getAPPPackage())) {
                        //只能是本包的类
                        beans.add(c2.getName());
                    }
                }
            }

            if(beans!=null && beans.size()>0){
                for(String names:beans){
                    AbstractBeanDefinition defined = (AbstractBeanDefinition)app.getBeanDefinition(names);
                    if(!(defined!=null && defined.getBeanClass().toGenericString().contains("class"))) {
                        beans.remove(names);
                    }
                }
            }

            buildProxy(beans);

        }

    }


    private void buildProxy(List<String> beans){


        List<Class> classList = new ArrayList<>();
        if(beans!=null && beans.size()>0){
            for(String names:beans){
                classList.add(createProxy(names));
            }
        }

        BeanHelper.registerBean(app.getBeanFactory(),false,true,classList);


        /*
        for(String name:beans){
            app.getBeanDefinition(name+"$AOP").setPrimary(true);
        }*/


        String value = "";
    }



    private Class createProxy(String bean){


        AbstractBeanDefinition defined = (AbstractBeanDefinition)app.getBeanDefinition(bean);
        Class beanClass = defined.getBeanClass();
        if(defined.getAutowireMode()!= GenericBeanDefinition.AUTOWIRE_NO) {
            //代表有自动注入的设置，此时改为按名称注入。
            //让代理类按类型自动注入
            defined.setAutowireMode(GenericBeanDefinition.AUTOWIRE_BY_NAME);
            defined.setPrimary(false);

            //defined.setAutowireCandidate(false);
            //defined.setPrimary(false);
        }
        Method [] ms = beanClass.getDeclaredMethods();

        Class superClass = BeanHelper.getSuperClass(beanClass);
        String className = bean+"$AOP$Proxy";

        //defined.getDependsOn()
        JavassistCode proxy = null;
        if(superClass.isInterface()) {
            //如果有接口实现接口，将代理类作为一个成员注入
            proxy = JavassistCode.createClass(className);
            proxy.addInterface(superClass.getName());
        }else{
            //类形式，将代理类作为一个成员注入
            proxy = JavassistCode.createClass(className,superClass.getName());
        }

        Class aopSuperClass = BeanHelper.getSuperClass(aopAdvice);
        String fieldAnnotationName = Resource.class.getName();
        Map annotationValue = new HashMap();

        try {
            annotationValue.put("name",bean);
            proxy.addField("target", "private " + superClass.getName() + " target;").addFieldAnnotation(fieldAnnotationName,annotationValue).buildFieldAnnotation();

            annotationValue.clear();
            annotationValue.put("name",aopAdvice.getName());
            proxy.addField("notice", "private " + aopSuperClass.getName() + " notice;").addFieldAnnotation(fieldAnnotationName,annotationValue).buildFieldAnnotation();


            StringBuilder code = new StringBuilder();
            if(ms!=null && ms.length>0){

                for(Method m:ms){
                    code.setLength(0);

                    String resultType = m.getReturnType().getName();
                    if(!resultType.equals("void"))
                    {
                        //定义返回值
                        code.append(" " + m.getReturnType().getName() + " result = null; ");
                    }else{
                        code.append(" Object result = null; ");
                    }
                    code.append(" notice.before(); ");

                    if(aopSuperClass.getName().equals(advice2)){
                        code.append(" boolean sign = notice.abort(); ");
                        code.append(" if(!sign)");
                        code.append("{");

                        Parameter [] p = m.getParameters();
                        List<String> paramsList = new ArrayList<>();
                        for(int i=0;i<p.length;i++) {
                            paramsList.add("args"+i);
                        }
                        String params = String.join(",",paramsList);

                        if(!resultType.equals("void")) {
                            code.append(" result = target." + m.getName() + "(" +params + ");");
                        }else{
                            code.append(" target." + m.getName() + "(" + params + ");");
                        }
                        code.append("}");

                        code.append(" notice.getResult(result); ");
                    }

                    code.append(" notice.after(); ");

                    if(!resultType.equals("void")) {
                        code.append(" return result; ");
                    }

                    String methodCode = proxy.anaylzeMethod(m,code.toString());

                    proxy.addMethod(m.getName(),methodCode);
                }
            }

        }catch (Exception e){

        }

        return proxy.toClass();
    }


}
